﻿using System;
using System.Collections.Generic;
using System.Text;
using ChessKit.ChessLogics.Internals;
using ChessKit.Common.Primitives;

namespace ChessKit.ChessLogics
{
  public class Board
  {
    public static readonly Board StartPosition = FromFenString(
      "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");

	  private CompactBoard _cells;
    private BoardInfo _info;
    private Move _prevMove;

    public Board Prev { get; private set; }
    public Move PrevMove { get { return _prevMove; } }

    public PieceColor SideOnMove
    {
      get { return _info.SideOnMove; }
    }
    public int MoveNumber
    {
      get { return _info.FullmoveNumber; }
    }

	  public bool IsCheck
	  {
		  get { return _info.GameState == GameState.Check; }
	  }
	  public bool IsMate
	  {
		  get { return _info.GameState == GameState.WhiteWin || _info.GameState == GameState.BlackWin; }
	  }

	  public IPiece GetPieceAt(Position position)
    {
      return Piece.Unpack(_cells[Pos.Pack(position)]);
    }

    #region ' MakeMove '

	  
    public List<Move> GetLegalMoves()
    {
		// BUG: Actually creates boards, but only returns moves!
      var res = new List<Move>(50);

      var sideOnMove = SideOnMove;
      for (var moveFrom = 0; moveFrom < 64; moveFrom++)
      {
        var piece = _cells[moveFrom];
        if (piece == CompactPiece.EmptyCell) continue;
        if (Piece.Color(piece) != sideOnMove) continue;
        for (var moveTo = 0; moveTo < 64; moveTo++)
        {
          if (moveFrom == moveTo) continue;
          var toPiece = _cells[moveTo];
          if (toPiece != CompactPiece.EmptyCell)
            if (Piece.Color(toPiece) == sideOnMove)
              continue;
	      if (!_cells.CanMoveBeValidFast(Piece.Type(piece), moveFrom, moveTo, sideOnMove))
		      continue;
          var validatedMove = new Board(this, piece, moveFrom, 
            moveTo, toPiece, PieceType.Queen, sideOnMove).PrevMove;
          if (!validatedMove.IsValid) continue;
          res.Add(new Move(Pos.Unpack(moveFrom), Pos.Unpack(moveTo), PieceType.Queen)
                        {
                          Hint = validatedMove.Hint,
                          ValidationProgress = validatedMove.ValidationProgress,
                        });
        }
      }
      return res;
    }

    public Board MakeMove(Move move)
    {
      return new Board(this, move);
    }

    private Board(Board src, Move move)
    {
      _prevMove = move;
      Prev = src;

      // Piece in the from cell?
      var moveFrom = Pos.Pack(move.From);
      var piece = src._cells[moveFrom];
      if (piece == CompactPiece.EmptyCell)
      {
        _prevMove.ValidationProgress = ValidationProgress.Invalid;
        _prevMove.Error = ValidationError.EmptyCell;
        return;
      }

      // Side to move?
      var color = Piece.Color(piece);
      if (color != src._info.SideOnMove)
        _prevMove.Warning |= ValidationWarning.WrongSideToMove;

      // Move to occupied cell?
      var moveTo = Pos.Pack(move.To);
      var toPiece = src._cells[moveTo];
      if (toPiece != CompactPiece.EmptyCell && Piece.Color(toPiece) == color)
      {
        _prevMove.ValidationProgress = ValidationProgress.Invalid;
        _prevMove.Error = ValidationError.OccupiedCell;
        return;
      }
      SetupBoard(src, piece, moveFrom, moveTo, toPiece,
        move.ProposedPromotion, color);
    }
    private Board(Board src, CompactPiece piece, int moveFrom,
      int moveTo, CompactPiece toPiece, PieceType proposedPromotion,
      PieceColor sideOnMove)
    {
      SetupBoard(src, piece, moveFrom, moveTo, toPiece, proposedPromotion, sideOnMove);
    }
    private void SetupBoard(Board src, CompactPiece piece,
      int moveFrom, int moveTo, CompactPiece toPiece, PieceType proposedPromotion,
      PieceColor color)
    {
      var sourceCells = src._cells;
      switch (Piece.Type(piece))
      {
        case PieceType.Pawn:
          var pawnMove = sourceCells.IsValidPawnMove(color, moveFrom, moveTo);
          if ((pawnMove & PawnMoveValidation.Valid) == 0)
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = (ValidationError)pawnMove;

            return;
          }
          _prevMove.Hint = (MoveHint)pawnMove;
          switch (pawnMove)
          {
            case PawnMoveValidation.EnPassant:
              if (!src._info.IsEnPassant || src._info.EnPassant != moveTo % 8)
              {
                _prevMove.ValidationProgress = ValidationProgress.Invalid;
                _prevMove.Error = ValidationError.PieceDoesntMoveThisWay;
                return;
              }
              break;
            case PawnMoveValidation.Promotion:
            case PawnMoveValidation.PromotionWithTake:
              piece = Piece.Get(proposedPromotion, color);
              break;
          }
          break;
        case PieceType.Bishop:
          var bishopMove = sourceCells.IsValidBishopMove(moveFrom, moveTo);
          if (bishopMove != SlideMoveValidation.Valid)
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = (ValidationError)bishopMove;
            return;
          }
          break;
        case PieceType.Knight:
          if (!sourceCells.IsValidKnightMove(moveFrom, moveTo))
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = ValidationError.PieceDoesntMoveThisWay;
            return;
          }
          break;
        case PieceType.Rook:
          _prevMove.Hint = MoveHint.RookMove;
          var rookMove = sourceCells.IsValidRookMove(moveFrom, moveTo);
          if (rookMove != SlideMoveValidation.Valid)
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = (ValidationError)rookMove;
            return;
          }
          break;
        case PieceType.Queen:
          var b = sourceCells.IsValidBishopMove(moveFrom, moveTo);
          var r = sourceCells.IsValidRookMove(moveFrom, moveTo);
          if (b != SlideMoveValidation.Valid && r != SlideMoveValidation.Valid)
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = (ValidationError)b;
            return;
          }
          break;
        case PieceType.King:
          if (sourceCells.IsValidKingMove(moveFrom, moveTo))
          {
            _prevMove.Hint = MoveHint.KingMove;
            break;
          }

          var kingMove = sourceCells.IsValidKingCastlingMove(moveFrom, moveTo);
          if (kingMove != KingMoveValidation.Valid)
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = (ValidationError)kingMove;
            return;
          }
          if (!CheckCastling(src, moveTo, color)) return;

          if (toPiece != CompactPiece.EmptyCell) // TODO: Should go into IsValidKingCastlingMove
          {
            _prevMove.ValidationProgress = ValidationProgress.Invalid;
            _prevMove.Error = ValidationError.CantTakePieceWithCastling;
            return;
          }
          // TODO: Optimize something (CantCastleUnderAttack?) if we know if it was check on the prev move?
          break;
      }

      if (toPiece != CompactPiece.EmptyCell)
      {
        _prevMove.Hint |= MoveHint.Take;
      }

      _info.Castling = src._info.Castling
        & ~KilledAvailability(moveTo)
        & ~KilledAvailability(moveFrom);

      _cells = new CompactBoard(sourceCells);

      _cells[moveTo] = piece;
      _cells[moveFrom] = CompactPiece.EmptyCell;

      _info.SideOnMove = color.Invert();

      _info.HalfmoveClock =
        (_prevMove.Hint & (MoveHint.Take | MoveHint.PawnMove)) != 0
        ? 0 : src._info.HalfmoveClock + 1;

      _info.FullmoveNumber = src._info.FullmoveNumber
        + (color == PieceColor.Black ? 1 : 0);

      if ((_prevMove.Hint & MoveHint.PawnDoubleMove) != 0)
      {
        _info.IsEnPassant = true;
        _info.EnPassant = moveFrom % 8;
      }
      else if ((_prevMove.Hint & MoveHint.EnPassant) != 0)
      {
        if (color == PieceColor.White)
          _cells[CompactBoard.A5 + src._info.EnPassant] = CompactPiece.EmptyCell;
        else
          _cells[CompactBoard.A4 + src._info.EnPassant] = CompactPiece.EmptyCell;
      }
      else if (color == PieceColor.White && _prevMove.Hint == MoveHint.KingSideCastling)
      {
        _cells[CompactBoard.H1] = CompactPiece.EmptyCell;
        _cells[CompactBoard.F1] = CompactPiece.WhiteRook;
      }
      else if (color == PieceColor.White && _prevMove.Hint == MoveHint.QueenSideCastling)
      {
        _cells[CompactBoard.A1] = CompactPiece.EmptyCell;
        _cells[CompactBoard.D1] = CompactPiece.WhiteRook;
      }
      else if (color == PieceColor.Black && _prevMove.Hint == MoveHint.KingSideCastling)
      {
        _cells[CompactBoard.H8] = CompactPiece.EmptyCell;
        _cells[CompactBoard.F8] = CompactPiece.BlackRook;
      }
      else if (color == PieceColor.Black && _prevMove.Hint == MoveHint.QueenSideCastling)
      {
        _cells[CompactBoard.A8] = CompactPiece.EmptyCell;
        _cells[CompactBoard.D8] = CompactPiece.BlackRook;
      }
      
      if (_cells.IsUnderCheck(src._info.SideOnMove))
      {
        _prevMove.ValidationProgress = ValidationProgress.Invalid;
        _prevMove.Error = ValidationError.KingUnderAttack;
        return;
      }
      if (_cells.IsUnderCheck(_info.SideOnMove))
      {
        _prevMove.Hint |= MoveHint.GivesCheck;
        _info.GameState = GameState.Check;
      }
      _prevMove.ValidationProgress = ValidationProgress.Valid;
    }

    private bool CheckCastling(Board src, int moveTo, PieceColor color)
    {
      if (color == PieceColor.White && moveTo == CompactBoard.G1)
      {
        _prevMove.Hint = MoveHint.KingSideCastling;
        if ((src._info.Castling & CastlingAvailability.WhiteKing) != 0)
          return true;
      }
      else if (color == PieceColor.White && moveTo == CompactBoard.C1)
      {
        _prevMove.Hint = MoveHint.QueenSideCastling;
        if ((src._info.Castling & CastlingAvailability.WhiteQueen) != 0)
          return true;
      }
      else if (color == PieceColor.Black && moveTo == CompactBoard.G8)
      {
        _prevMove.Hint = MoveHint.KingSideCastling;
        if ((src._info.Castling & CastlingAvailability.BlackKing) != 0)
          return true;
      }
      else if (color == PieceColor.Black && moveTo == CompactBoard.C8)
      {
        _prevMove.Hint = MoveHint.QueenSideCastling;
        if ((src._info.Castling & CastlingAvailability.BlackQueen) != 0)
          return true;
      }

      _prevMove.ValidationProgress = ValidationProgress.Invalid;
      _prevMove.Error = ValidationError.NoCastlingThisWay;
      return false;
    }

    private static CastlingAvailability KilledAvailability(int pos)
    {
      switch (pos)
      {
        case CompactBoard.A1: return CastlingAvailability.WhiteQueen;
        case CompactBoard.E1: return CastlingAvailability.White;
        case CompactBoard.H1: return CastlingAvailability.WhiteKing;
        case CompactBoard.A8: return CastlingAvailability.BlackQueen;
        case CompactBoard.E8: return CastlingAvailability.Black;
        case CompactBoard.H8: return CastlingAvailability.BlackKing;
        default: return CastlingAvailability.None;
      }
    }
    #endregion

    #region ' FEN '

    private Board() { }

	  private Board(CompactBoard cells)
	  {
		  _cells = cells;
	  }

	  /// <summary> Load position from FEN string </summary>
    /// <param name="fen">FEN string</param>
    /// <remarks>http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation</remarks>
    public static Board FromFenString(string fen)
    {
      var offset = 0;
      var board = new Board(new CompactBoard(0));
      try
      {
        board.LoadPiecePlacementSection(fen, ref offset);
        board.LoadActiveColorSection(fen, ref offset);
        board.LoadCastlingAvailabilitySection(fen, ref offset);
        board.LoadEnPassantSection(fen, ref offset);
        board.LoadHalfmoveClockSection(fen, ref offset);
        board.LoadFullmoveNumberSection(fen, ref offset);
      }
      catch (IndexOutOfRangeException x)
      {
        throw new FormatException("Unexpected end of FEN string", x);
      }
      return board;
    }

    private void LoadPiecePlacementSection(string fen, ref int i)
    {
      for (var sq = 63; ; i++)
      {
        if (fen[i] == ' ') break;
        if (fen[i] >= '1' && fen[i] <= '9') sq -= fen[i] - '0';
        else if ('/' == fen[i]) { }
        else
        {
          _cells[(sq / 8) * 8 + 7 - sq % 8] = Piece.Parse(fen[i]);
          sq--;
        }
      }
      i++; // Skip the space
    }
    private void LoadActiveColorSection(string fen, ref int i)
    {
      _info.SideOnMove = Color.Parse(fen[i++]);
      if (fen[i] != ' ')
        throw new FormatException("Unexpected symbol");
      i++; // skip the space 
    }
    private void LoadCastlingAvailabilitySection(string fen, ref int i)
    {
      var flags = default(CastlingAvailability);
      for (; ; i++)
      {
        if (fen[i] == '-') { }
        else if (fen[i] == 'K') flags |= CastlingAvailability.WhiteKing;
        else if (fen[i] == 'Q') flags |= CastlingAvailability.WhiteQueen;
        else if (fen[i] == 'k') flags |= CastlingAvailability.BlackKing;
        else if (fen[i] == 'q') flags |= CastlingAvailability.BlackQueen;
        else if (fen[i] == ' ') break;
        else throw new FormatException("illegal character");
      }
      _info.Castling = flags;
      i++; // Skip the space
    }
    private void LoadEnPassantSection(string fen, ref int i)
    {
      if (fen[i] != '-')
      {
        _info.EnPassant = EnPassant.Parse(fen[i]);
        _info.IsEnPassant = true;
      }
      i++; // Skip the rank, or skip the '-'
      i++; // Skip the space
    }
    private void LoadHalfmoveClockSection(string fen, ref int i)
    {
      var res = 0;
      for (; ; i++)
      {
        if (fen[i] == ' ') break;
        if (fen[i] >= '0' && fen[i] <= '9')
          res = res * 10 + fen[i] - '0';
      }
      _info.HalfmoveClock = res;
      i++; // Skip the space
    }
    private void LoadFullmoveNumberSection(string fen, ref int i)
    {
      var res = 0;
      for (; i < fen.Length; i++)
        if (fen[i] >= '0' && fen[i] <= '9')
          res = res * 10 + fen[i] - '0';
      _info.FullmoveNumber = res;
    }

    public string ToFenString()
    {
      var fen = new StringBuilder(77);
      for (int empty = 0, sq = 63; sq >= 0; sq--)
      {
        var idx = (sq / 8) * 8 + 7 - sq % 8;
        if (_cells[idx] == CompactPiece.EmptyCell)
        {
          empty++;
          if (0 == sq % 8)
          {
            if (empty != 0) fen.Append((char)('0' + empty));
            if (sq != 0) fen.Append('/');
            empty = 0;
          }
          continue;
        }

        if (empty != 0) fen.Append((char)('0' + empty));
        fen.Append(Piece.Unpack(_cells[idx]).Symbol);
        empty = 0;
        if (sq != 0 && sq % 8 == 0) fen.Append('/');
      }

      fen.Append(' ');
      fen.Append(_info.SideOnMove == PieceColor.White ? 'w' : 'b');

      fen.Append(' ');
      var castling = _info.Castling;
      if (castling == CastlingAvailability.None)
      {
        fen.Append('-');
      }
      else
      {
        if ((castling & CastlingAvailability.WhiteKing) != 0) fen.Append('K');
        if ((castling & CastlingAvailability.WhiteQueen) != 0) fen.Append('Q');
        if ((castling & CastlingAvailability.BlackKing) != 0) fen.Append('k');
        if ((castling & CastlingAvailability.BlackQueen) != 0) fen.Append('q');
      }

      fen.Append(' ');
      if (!_info.IsEnPassant)
      {
        fen.Append('-');
      }
      else
      {
        fen.Append("abcdefgh"[_info.EnPassant]);
        fen.Append(_info.SideOnMove == PieceColor.White ? '6' : '3');
      }

      fen.Append(' ');
      fen.Append(_info.HalfmoveClock);

      fen.Append(' ');
      fen.Append(_info.FullmoveNumber);

      return fen.ToString();
    }

    #endregion

	  public IPiece this[Position index]
	  {
		  get { return Piece.Unpack(_cells[Pos.Pack(index)]); }
	  }
  }
}

